/*
 *    Copyright 2009-2021 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.reflection.factory;

import org.apache.ibatis.reflection.ReflectionException;
import org.apache.ibatis.reflection.Reflector;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.stream.Collectors;

/** @author Clinton Begin */
public class DefaultObjectFactory implements ObjectFactory, Serializable {

  private static final long serialVersionUID = -8855120656740914948L;

  @Override
  public <T> T create(Class<T> type) {
    return create(type, null, null);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T create(
      Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    // 判断类的接口类型
    Class<?> classToCreate = resolveInterface(type);
    // we know types are assignable
    // 实际创建对象的方法
    return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
  }

  /**
   * 生成类对应的对象实例
   *
   * @param type 类
   * @param constructorArgTypes 构造器参数类型
   * @param constructorArgs 构造器参数
   * @param <T>
   * @return
   */
  private <T> T instantiateClass(
      Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      // 默认的构造器
      if (constructorArgTypes == null || constructorArgs == null) {
        // 默认的无参构造器
        constructor = type.getDeclaredConstructor();
        try {
          return constructor.newInstance();
        } catch (IllegalAccessException e) {
          // 判断访问是否可控
          if (Reflector.canControlMemberAccessible()) {
            constructor.setAccessible(true);
            return constructor.newInstance();
          } else {
            throw e;
          }
        }
      }
      // 获取指定参数的构造器
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));
      try {
        return constructor.newInstance(constructorArgs.toArray(new Object[0]));
      } catch (IllegalAccessException e) {
        if (Reflector.canControlMemberAccessible()) {
          constructor.setAccessible(true);
          return constructor.newInstance(constructorArgs.toArray(new Object[0]));
        } else {
          throw e;
        }
      }
    } catch (Exception e) {
      // 对参数类型和参数值分别做一个拼接，抛出异常
      String argTypes =
          Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList).stream()
              .map(Class::getSimpleName)
              .collect(Collectors.joining(","));
      String argValues =
          Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList).stream()
              .map(String::valueOf)
              .collect(Collectors.joining(","));
      throw new ReflectionException(
          "Error instantiating "
              + type
              + " with invalid types ("
              + argTypes
              + ") or values ("
              + argValues
              + "). Cause: "
              + e,
          e);
    }
  }

  /**
   * 判断传入的类是哪种接口类型
   *
   * @param type
   * @return
   */
  protected Class<?> resolveInterface(Class<?> type) {
    Class<?> classToCreate;
    if (type == List.class || type == Collection.class || type == Iterable.class) {
      classToCreate = ArrayList.class;
    } else if (type == Map.class) {
      classToCreate = HashMap.class;
    } else if (type == SortedSet.class) { // issue #510 Collections Support
      classToCreate = TreeSet.class;
    } else if (type == Set.class) {
      classToCreate = HashSet.class;
    } else {
      classToCreate = type;
    }
    return classToCreate;
  }

  @Override
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }
}
